﻿using System;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Entity.Scene;

namespace RayDen.Library.Components.Surface
{
    public struct BsdfSampleData
    {
        public Vector Wi;
        public RgbSpectrum F;
        //public SampledSpectrum Spd;
        public float Pdf, Lambda;
        public BrdfType Type;
        public bool SpecularBounce
        {
            get { return Type.Has(BrdfType.Specular); }
        }
    }

    [Serializable]
    public abstract class SurfaceBsdf : BrdfBase
    {

        protected SurfaceBsdf()
        {
            
        }
        protected SurfaceBsdf(ONB fr, MaterialInfo mt) 
        {
            this.nn = fr.Normal();
            this.tn = fr.Tangent();
            this.sn = fr.Binormal();
        }

        public override bool IsLightSource()
        {
            return false;
        }

        public override bool IsRefractive()
        {
            return false;
        }

        public abstract void f(ref Vector wo, ref Vector wi, ref Normal N, ref RgbSpectrum in_fs, out RgbSpectrum fs);



        public virtual void Sample_f(ref Vector wo, ref Normal N, ref Normal shadeN, ref RgbSpectrum in_f, float u0, float u1, float u2, ref SurfaceTextureData surfaceData, out BsdfSampleData result)
        {
            EvalParams(ref surfaceData);
            result = new BsdfSampleData() { Type = this.Type };
            CreateFrame(ref N);
            var wi = MC.CosineSampleHemisphere(u1, u2);
            if (wo.z < 0f) wi.z *= -1f;
            result.Pdf = Pdf(ref wo, ref wi, BxDFTypes.BSDF_ALL);
            f(ref wo, ref wi, ref shadeN, ref in_f, out result.F);
            wi = LocalToWorld(ref wi);
            result.Wi = wi;
            //	return f(wo, *wi);
        }

        protected virtual void EvalParams(ref SurfaceTextureData surfaceData)
        {
            
        }

        public static Vector GlossyReflection(ref Vector wo, float exponent, ref Normal shadN, float u0, float u1)
        {
            var shadeN = shadN;
            float phi = 2f * MathLab.M_PI * u0;
            float cosTheta = (float)MathLab.Pow(1f - u1, exponent);
            float sinTheta = MathLab.Sqrt(1f - cosTheta * cosTheta);
            float x = (float)Math.Cos(phi) * sinTheta;
            float y = (float)Math.Sin(phi) * sinTheta;
            float z = cosTheta;

            Vector dir = -wo;
            float dp = Vector.Dot(ref shadeN, ref dir);
            Vector w = dir - (2f * dp) * shadeN.ToVec();

            Vector u;
            if (Math.Abs(shadeN.x) > .1f)
            {
                var a = new Vector(0f, 1f, 0f);
                u = Vector.Cross(ref a, ref w);
            }
            else
            {
                Vector a = new Vector(1f, 0f, 0f);
                u = Vector.Cross(ref a, ref w);
            }
            u = u.Normalize();
            Vector v = Vector.Cross(ref w, ref u);

            return x * u + y * v + z * w;
        }

        public virtual float Pdf(ref Vector wo, ref Vector wi, BxDFTypes bxDFType)
        {
            return MathLab.INVTWOPI;
        }

        public void CreateFrame(ref Normal n)
        {
            nn = n.ToVec().Normalize();
            Vector.CoordinateSystem(ref nn, out sn, out tn);
            //frameCreated = true;
        }

        protected internal Vector tn, sn, nn;
        protected bool frameCreated = false;

        public Vector WorldToLocal(ref Vector v)
        {
            return new Vector(Vector.Dot(ref v, ref  sn), Vector.Dot(ref v, ref tn), Vector.Dot(ref v, ref nn));
        }
        public Vector LocalToWorld(Vector v)
        {
            return new Vector(sn.x * v.x + tn.x * v.y + nn.x * v.z,
                               sn.y * v.x + tn.y * v.y + nn.y * v.z,
                               sn.z * v.x + tn.z * v.y + nn.z * v.z);
        }
        public Vector LocalToWorld(ref Vector v)
        {
            return new Vector(sn.x * v.x + tn.x * v.y + nn.x * v.z,
                               sn.y * v.x + tn.y * v.y + nn.y * v.z,
                               sn.z * v.x + tn.z * v.y + nn.z * v.z);
        }
    };
}